//@author A0081229H
package storage;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Iterator;

public class External {
	private static final String SPACE = " ";
	private static final String NEXTLINE = "\r\n";
	private static final String TRUE = "true";
	private static final String FALSE = "false";
	private static final String NULL = "null";
	
	private static final String FLAG_EMPTY = "0";
	private static final String FLAG_ONE = "1";
	private static final String FLAG_TWO = "2";
	private static final String FLAG_THREE = "3";
	
	private static final String ENCODING = "UTF-8";
	
	private static final int DESCRIPTION = 0;
	private static final int STARTDATE_FLAG = 1;
	private static final int STARTDATE = 2;
	private static final int ENDDATE_FLAG = 3;
	private static final int ENDDATE = 4;
	private static final int STARTTIME_FLAG = 5;
	private static final int STARTTIME = 6;
	private static final int ENDTIME_FLAG = 7;
	private static final int ENDTIME = 8;
	private static final int DONE = 9;
	private static final int COPY = 10;
	private static final int ATTR_SIZE = 11;
	
	private String filePath;
	private Storage storage;
	private ClashingMaintenance clashingMaintenance;
	
	// Constructor
	
	public External(String pFilePath) throws NumberFormatException, Exception {
		filePath = pFilePath;
		storage = Storage.getInstance();
		clashingMaintenance = ClashingMaintenance.getInstance();
	}
	
	// Download from External
	
	public void load() throws NumberFormatException, Exception {
		
		File fr = new File(filePath);
		if (!fr.exists()) {
			PrintWriter pw = new PrintWriter(new FileWriter(filePath));
			pw.close();
		}
		FileInputStream fis = new FileInputStream(fr);
		InputStreamReader isr = new InputStreamReader(fis, ENCODING);
		BufferedReader br = new BufferedReader(isr);
		String cLine;
		while ((cLine = br.readLine()) != null) {
			processExternalStorageLine(cLine);
		}
		br.close();
	}

	private void processExternalStorageLine(String pLine) throws NumberFormatException, Exception {
		String[] externalAttributes = new String[ATTR_SIZE];
		convertToFormat(pLine, externalAttributes);
		Task task = retrieveTask(externalAttributes);
		clashingMaintenance.maintainClashesAdd(task, storage.getTaskList());
		
		storage.getTaskList().add(task);
		task.addObs(Storage.getInstance());
	}
	
	private Task retrieveTask(String[] externalAttributes) throws Exception {
		assert (externalAttributes == null): Helper.ERROR_NULL;
		String description;
		Time startTime, endTime;
		Date startDate, endDate;
		TimeRange timeRange;
		DateRange dateRange;
		boolean done;
		int copy;
		
		description = externalAttributes[DESCRIPTION];

		startDate = retrieveDate(externalAttributes, STARTDATE_FLAG, STARTDATE);
		endDate = retrieveDate(externalAttributes, ENDDATE_FLAG, ENDDATE);
		dateRange = new DateRange(startDate, endDate);

		startTime = retrieveTime(externalAttributes, STARTTIME_FLAG, STARTTIME);
		endTime = retrieveTime(externalAttributes, ENDTIME_FLAG, ENDTIME);
		timeRange = new TimeRange(startTime, endTime);

		done = Boolean.parseBoolean(externalAttributes[DONE]);
		copy = Integer.parseInt(externalAttributes[COPY]);

		Task task = new Task(description, dateRange, timeRange);
		
		task.setStatus(done);
		task.setCopyCounter(copy);
		
		return task;
	}
	
	private void convertToFormat(String pLine, String[] externalAttributes) {
		assert (externalAttributes == null): Helper.ERROR_NULL;
		int descFlag = Integer.parseInt(pLine.split(" ")[0]);
		String[] preAttr = pLine.split(" ");
		String s = "";
		for (int i=1; i<descFlag+1; i++) {
			s += preAttr[i];
			if (i!=descFlag) {s += SPACE;};
		}
		
		externalAttributes[DESCRIPTION] = s;
		for (int i=1;i<11;i++) {
			String t = preAttr[descFlag+i];
			externalAttributes[i] = t;
		}
	}
		
	private Date retrieveDate(String[] externalAttributes, int flag, int dateType) throws Exception {
		assert (externalAttributes == null): Helper.ERROR_NULL;
		Date startDate;
		int date, yr, mth, day;
		switch (externalAttributes[flag]) {
		case "0":
			startDate = new Date();
			break;
		case "1":
			date = Integer.parseInt(externalAttributes[dateType]);
			yr = date;
			startDate = new Date(yr);
			break;
		case "2":
			date = Integer.parseInt(externalAttributes[dateType]);
			yr = (int) Math.floor(date / 100);
			mth = date % 100;
			startDate = new Date(yr, mth);
			break;
		case "3":
			date = Integer.parseInt(externalAttributes[dateType]);
			yr = (int) Math.floor(date / 10000);
			day = date % 100;
			mth = ((date % 10000) - day) / 100;
			startDate = new Date(yr, mth, day);
			break;
		default:
			throw new Exception();
		}
		return startDate;
	}
	
	private Time retrieveTime(String[] externalAttributes, int flag, int timeType) throws Exception {
		assert (externalAttributes == null): Helper.ERROR_NULL;
		Time startTime;
		int time, hr, min, sec;
		switch (externalAttributes[flag]) {
		case "0":
			startTime = new Time();
			break;
		case "1":
			time = Integer.parseInt(externalAttributes[timeType]);
			hr = time;
			startTime = new Time(hr);
			break;
		case "2":
			time = Integer.parseInt(externalAttributes[timeType]);
			hr = (int) Math.floor(time / 100);
			min = time % 100;
			startTime = new Time(hr, min);
			break;
		case "3":
			time = Integer.parseInt(externalAttributes[timeType]);
			hr = (int) Math.floor(time / 10000);
			sec = time % 100;
			min = ((time % 10000) - sec) / 100;
			startTime = new Time(hr, min, sec);
			break;
		default:
			throw new Exception();
		}
		return startTime;
	}
		
	// Upload to External
	
	public void update() throws Exception {
		PrintWriter pw = new PrintWriter(new FileWriter(filePath));
		Storage storage = Storage.getInstance();
		int counter = 0;
		for (Iterator<Task> it = storage.iterator(); it.hasNext();) {
			Task t = it.next();
			updateAttributes(pw, t, counter);
			counter++;
		}
		pw.close();
	}
	
	private void updateAttributes(PrintWriter pw, Task t, int c) {
		assert (pw == null || t == null): Helper.ERROR_NULL;
		updateDescription(pw, t);			
		updateStartDate(pw, t);
		updateEndDate(pw, t);
		updateStartTime(pw, t);
		updateEndTime(pw, t);
		updateDoneStatus(pw, t);
		updateCopyCounter(pw, t);
		finishEntry(pw, c);
	}
	
	private void updateDescription(PrintWriter pw, Task t) {
		assert (pw == null || t == null): Helper.ERROR_NULL;
		pw.write(t.getDescription().split(" ").length + SPACE);
		pw.write(t.getDescription() + SPACE);
	}
	
	private void updateStartDate(PrintWriter pw, Task t) {
		assert (pw == null || t == null): Helper.ERROR_NULL;
		Date startDate = t.getDateRange().getStartDate();
		updateDate(pw, startDate);
	}
	
	private void updateEndDate(PrintWriter pw, Task t) {
		assert (pw == null || t == null): Helper.ERROR_NULL;
		Date endDate = t.getDateRange().getEndDate();
		updateDate(pw, endDate);
	}
	
	private void updateStartTime(PrintWriter pw, Task t) {
		assert (pw == null || t == null): Helper.ERROR_NULL;
		Time startTime = t.getTimeRange().getStartTime();
		updateTime(pw, startTime);
	}
	
	private void updateEndTime(PrintWriter pw, Task t) {
		assert (pw == null || t == null): Helper.ERROR_NULL;
		Time endTime = t.getTimeRange().getEndTime();
		updateTime(pw, endTime);
	}
	
	private void updateDoneStatus(PrintWriter pw, Task t) {
		assert (pw == null || t == null): Helper.ERROR_NULL;
		if (t.getDoneStatus() == true) {
			pw.write(TRUE + SPACE);
		} else {
			pw.write(FALSE + SPACE);
		}
	}
	
	private void updateCopyCounter(PrintWriter pw, Task t) {
		assert (pw == null || t == null): Helper.ERROR_NULL;
		pw.write(((Integer) t.getCopyCounter()).toString());
	}
	
	private void finishEntry(PrintWriter pw, int c) {
		assert (pw == null): Helper.ERROR_NULL;
		int lastEntry = storage.getSize() - 1;
		if (c != lastEntry) {
			pw.write(NEXTLINE);
		}
	}
	
	private void updateDate(PrintWriter pw, Date d) {
		assert (pw == null || d == null): Helper.ERROR_NULL;
		updateDateTime(d, pw);
	}
	
	private void updateTime(PrintWriter pw, Time t) {
		assert (pw == null || t == null): Helper.ERROR_NULL;
		updateDateTime(t, pw);
	}
	
	private void updateDateTime(DateTime dt, PrintWriter pw) {
		assert (pw == null || dt == null): Helper.ERROR_NULL;
		if (dt.isEmpty()) {
			pw.write(FLAG_EMPTY + SPACE);
			pw.write(NULL + SPACE);
		} else if (dt.getSecondAttribute() == null) {
			pw.write(FLAG_ONE + SPACE);
			Helper.writeIntToStr(dt.getFirstAttribute(), pw);
			pw.write(SPACE);
		} else if (dt.getThirdAttribute() == null) {
			pw.write(FLAG_TWO + SPACE);
			Helper.writeIntToStr(dt.getFirstAttribute(), pw);
			Helper.processZero(pw, dt.getSecondAttribute());
			Helper.writeIntToStr(dt.getSecondAttribute(), pw);
			pw.write(SPACE);
		} else {
			pw.write(FLAG_THREE + SPACE);
			Helper.writeIntToStr(dt.getFirstAttribute(), pw);
			Helper.processZero(pw, dt.getSecondAttribute());
			Helper.writeIntToStr(dt.getSecondAttribute(), pw);
			Helper.processZero(pw, dt.getThirdAttribute());
			Helper.writeIntToStr(dt.getThirdAttribute(), pw);
			pw.write(SPACE);
		}
	}
}
